package egar

import "math"

type BallType = int8

const (
	TypeFood BallType = iota
	TypeVirus
	TypeSpore
	TypeCell
)

type Ball struct {
	ID                          uint32
	Type                        BallType
	X, Y, Mass, Radius, Radius2 float64
	Color                       uint32
	Removed                     bool
	Angle                       float64
	OwnerPlayer                 *Player // only used by cell
	CellsIndex                  int     // only used by cell, index in ownerPlayer.cells
	boostMove                   bool
	boostX, boostY              float64
	velocityX, velocitY         float64 // 加速度
	boostMaxSpeed               float64
}

func (b *Ball) SetMass(mass float64) {
	b.Mass = mass
	b.Radius = MassToRadius(mass)
	b.Radius2 = b.Radius * b.Radius
}

func (b *Ball) AddMass(mass float64) {
	b.SetMass(b.Mass + mass)
}

func (b *Ball) GetAngle(x, y float64) float64 {
	return math.Atan2(y-b.Y, x-b.X)
}

func (b *Ball) GetDistance(x, y float64) float64 {
	return math.Hypot(x-b.X, y-b.Y)
}

func (b *Ball) checkCollision(target *Ball) (dx, dy, distance float64, check bool) {
	dx = target.X - b.X
	dy = target.Y - b.Y
	distance = math.Hypot(dx, dy)
	check = distance < b.Radius+target.Radius
	return
}

func (b *Ball) setBoost(x, y float64) {
	b.boostMove = true
	b.boostX = x
	b.boostY = y
	b.boostMaxSpeed = math.Hypot(b.boostX, b.boostY)
}

func NewFoodBall(minMass, maxMass float64) *Ball {
	x, y := RandomPosition()
	ball := &Ball{
		ID:          generateBallID(),
		Type:        TypeFood,
		X:           x,
		Y:           y,
		Color:       RandomColor(),
		Removed:     false,
		Angle:       0,
		OwnerPlayer: nil,
	}
	ball.SetMass(RandomRange(minMass, maxMass))
	return ball
}

func NewSporeBall(x, y float64, mass float64, color uint32) *Ball {
	ball := &Ball{
		ID:          generateBallID(),
		Type:        TypeSpore,
		X:           x,
		Y:           y,
		Color:       color,
		Removed:     false,
		Angle:       0,
		OwnerPlayer: nil,
		boostMove:   false,
	}
	ball.SetMass(mass)
	return ball
}

func NewVirusBall(minMass float64, maxMass float64) *Ball {
	x, y := RandomPosition()
	ball := &Ball{
		ID:          generateBallID(),
		Type:        TypeVirus,
		X:           x,
		Y:           y,
		Color:       0x22F63A,
		Removed:     false,
		Angle:       0,
		OwnerPlayer: nil,
	}
	ball.SetMass(RandomRange(minMass, maxMass))
	return ball
}

func NewCellBall(x, y float64, color uint32, mass float64, ownerPlayer *Player) *Ball {
	ball := &Ball{
		ID:          generateBallID(),
		Type:        TypeCell,
		X:           x,
		Y:           y,
		Color:       color,
		Removed:     false,
		Angle:       0,
		OwnerPlayer: ownerPlayer,
		boostMove:   false,
	}
	ball.SetMass(mass)
	return ball
}

func updateSpore(spore *Ball, deltaTime float64) {
	spore.X += spore.boostX * (deltaTime * 1.5)
	spore.Y += spore.boostY * (deltaTime * 1.5)
	speed := math.Max(math.Hypot(spore.boostX, spore.boostY), 0.0000001) / 35
	spore.boostX /= 1 + speed*deltaTime + deltaTime
	spore.boostY /= 1 + speed*deltaTime + deltaTime
}

func updateCell(cell *Ball, deltaTime float64) {
	{
		// boost
		cell.X += cell.boostX * (deltaTime * 1.5)
		cell.Y += cell.boostY * (deltaTime * 1.5)
		speed := math.Max(math.Hypot(cell.boostX, cell.boostY), 0.0000001) / 40
		cell.boostX /= 1 + speed*deltaTime + deltaTime
		cell.boostY /= 1 + speed*deltaTime + deltaTime
	}
	{
		// move
		distance := cell.GetDistance(cell.OwnerPlayer.TargetX, cell.OwnerPlayer.TargetY)
		if distance < 1 {
			return
		}
		cell.Angle = cell.GetAngle(cell.OwnerPlayer.TargetX, cell.OwnerPlayer.TargetY)
		speed := 20 * math.Pow(cell.Radius, -0.4)
		cell.X = cell.X + math.Cos(cell.Angle)*speed*deltaTime
		cell.Y = cell.Y + math.Sin(cell.Angle)*speed*deltaTime
	}
}

func resolveCellCollision(ball, target *Ball, deltaTime float64) {
	dx, dy, distance, check := ball.checkCollision(target)
	if !check {
		return
	}
	if distance == 0 {
		distance = 0.0001
		dx += 0.0001
		dy += 0.0001
	}
	push := (ball.Radius + target.Radius - distance) / distance
	if push <= 0.01 {
		return
	}
	rt := ball.Radius + target.Radius
	r1 := push * ball.Radius / rt
	r2 := push * target.Radius / rt
	// v := math.Max((1-math.Max(math.Hypot(ball.boostX, ball.boostY), math.Hypot(target.boostX, target.boostY))/math.Max(ball.boostMaxSpeed, target.boostMaxSpeed))*2-0.6, 0)
	ball.X -= dx * r2 * deltaTime * 8
	ball.Y -= dy * r2 * deltaTime * 8
	target.X += dx * r1 * deltaTime * 8
	target.Y += dy * r1 * deltaTime * 8
}
